/*-------------------------------------------------------------------------
 *
 * event_trigger.c
 *	  PostgreSQL 事件触发器支持代码。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/commands/event_trigger.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/trigger.h"
#include "funcapi.h"
#include "lib/ilist.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "pgstat.h"
#include "tcop/deparse_utility.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/evtcache.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"

typedef struct EventTriggerQueryState
{
	/* 该状态对象的内存上下文 */
	MemoryContext cxt;

	/* sql_drop */
	slist_head	SQLDropList;
	bool		in_sql_drop;

	/* table_rewrite */
	Oid			table_rewrite_oid;	/* InvalidOid，或设置为 table_rewrite 事件 */
	int			table_rewrite_reason;	/* AT_REWRITE 原因 */

	/* 支持命令收集 */
	bool		commandCollectionInhibited;
	CollectedCommand *currentCommand;
	List	   *commandList;	/* 收集命令的列表；见 deparse_utility.h */
	struct EventTriggerQueryState *previous;
} EventTriggerQueryState;

static EventTriggerQueryState *currentEventTriggerState = NULL;

/* 支持被删除的对象 */
typedef struct SQLDropObject
{
	ObjectAddress address;
	const char *schemaname;
	const char *objname;
	const char *objidentity;
	const char *objecttype;
	List	   *addrnames;
	List	   *addrargs;
	bool		original;
	bool		normal;
	bool		istemp;
	slist_node	next;
} SQLDropObject;

static void fc_AlterEventTriggerOwner_internal(Relation fc_rel,
											HeapTuple fc_tup,
											Oid fc_newOwnerId);
static void fc_error_duplicate_filter_variable(const char *fc_defname);
static Datum fc_filter_list_to_array(List *fc_filterlist);
static Oid	fc_insert_event_trigger_tuple(const char *fc_trigname, const char *fc_eventname,
									   Oid fc_evtOwner, Oid fc_funcoid, List *fc_tags);
static void fc_validate_ddl_tags(const char *fc_filtervar, List *fc_taglist);
static void fc_validate_table_rewrite_tags(const char *fc_filtervar, List *fc_taglist);
static void fc_EventTriggerInvoke(List *fc_fn_oid_list, EventTriggerData *fc_trigdata);
static const char *fc_stringify_grant_objtype(ObjectType fc_objtype);
static const char *fc_stringify_adefprivs_objtype(ObjectType fc_objtype);

/*
 * 创建一个事件触发器。
 */
Oid CreateEventTrigger(CreateEventTrigStmt *fc_stmt)
{
	HeapTuple	fc_tuple;
	Oid			fc_funcoid;
	Oid			fc_funcrettype;
	Oid			fc_evtowner = GetUserId();
	ListCell   *fc_lc;
	List	   *fc_tags = NULL;

	/*
	 * 如果能够允许数据库所有者甚至普通用户这样做，那将是很好，但是
	 * 显然存在特权升级的风险，必须首先找到解决办法。
	 */
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied to create event trigger \"%s\"",
						fc_stmt->trigname),
				 errhint("Must be superuser to create an event trigger.")));

	/* 验证事件名称。 */
	if (strcmp(fc_stmt->eventname, "ddl_command_start") != 0 &&
		strcmp(fc_stmt->eventname, "ddl_command_end") != 0 &&
		strcmp(fc_stmt->eventname, "sql_drop") != 0 &&
		strcmp(fc_stmt->eventname, "table_rewrite") != 0)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("unrecognized event name \"%s\"",
						fc_stmt->eventname)));

	/* 验证过滤条件。 */
	foreach(fc_lc, fc_stmt->whenclause)
	{
		DefElem    *fc_def = (DefElem *) lfirst(fc_lc);

		if (strcmp(fc_def->defname, "tag") == 0)
		{
			if (fc_tags != NULL)
				fc_error_duplicate_filter_variable(fc_def->defname);
			fc_tags = (List *) fc_def->arg;
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("unrecognized filter variable \"%s\"", fc_def->defname)));
	}

	/* 验证标签列表（如果有的话）。 */
	if ((strcmp(fc_stmt->eventname, "ddl_command_start") == 0 ||
		 strcmp(fc_stmt->eventname, "ddl_command_end") == 0 ||
		 strcmp(fc_stmt->eventname, "sql_drop") == 0)
		&& fc_tags != NULL)
		fc_validate_ddl_tags("tag", fc_tags);
	else if (strcmp(fc_stmt->eventname, "table_rewrite") == 0
			 && fc_tags != NULL)
		fc_validate_table_rewrite_tags("tag", fc_tags);

	/*
	 * 如果同名的事件触发器已经存在，则给用户一个友好的错误消息。
	 */
	fc_tuple = SearchSysCache1(EVENTTRIGGERNAME, CStringGetDatum(fc_stmt->trigname));
	if (HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("event trigger \"%s\" already exists",
						fc_stmt->trigname)));

	/* 查找并验证触发函数。 */
	fc_funcoid = LookupFuncName(fc_stmt->funcname, 0, NULL, false);
	fc_funcrettype = get_func_rettype(fc_funcoid);
	if (fc_funcrettype != EVENT_TRIGGEROID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("function %s must return type %s",
						NameListToString(fc_stmt->funcname), "event_trigger")));

	/* 插入目录条目。 */
	return fc_insert_event_trigger_tuple(fc_stmt->trigname, fc_stmt->eventname,
									  fc_evtowner, fc_funcoid, fc_tags);
}

/*
 * 验证 DDL 命令标签。
 */
static void fc_validate_ddl_tags(const char *fc_filtervar, List *fc_taglist)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_taglist)
	{
		const char *fc_tagstr = strVal(lfirst(fc_lc));
		CommandTag	fc_commandTag = GetCommandTagEnum(fc_tagstr);

		if (fc_commandTag == CMDTAG_UNKNOWN)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("filter value \"%s\" not recognized for filter variable \"%s\"",
							fc_tagstr, fc_filtervar)));
		if (!command_tag_event_trigger_ok(fc_commandTag))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			/* translator: %s 表示一个 SQL 语句名称 */
					 errmsg("event triggers are not supported for %s",
							fc_tagstr)));
	}
}

/*
 * 验证事件 table_rewrite 的 DDL 命令标签。
 */
static void fc_validate_table_rewrite_tags(const char *fc_filtervar, List *fc_taglist)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_taglist)
	{
		const char *fc_tagstr = strVal(lfirst(fc_lc));
		CommandTag	fc_commandTag = GetCommandTagEnum(fc_tagstr);

		if (!command_tag_table_rewrite_ok(fc_commandTag))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			/* translator: %s 表示一个 SQL 语句名称 */
					 errmsg("event triggers are not supported for %s",
							fc_tagstr)));
	}
}

/*
 * 抱怨重复的过滤变量。
 */
static void fc_error_duplicate_filter_variable(const char *fc_defname)
{
	ereport(ERROR,
			(errcode(ERRCODE_SYNTAX_ERROR),
			 errmsg("filter variable \"%s\" specified more than once",
					fc_defname)));
}

/*
 * 插入新的 pg_event_trigger 行并记录依赖关系。
 */
static Oid fc_insert_event_trigger_tuple(const char *fc_trigname, const char *fc_eventname, Oid fc_evtOwner,
						   Oid fc_funcoid, List *fc_taglist)
{
	Relation	fc_tgrel;
	Oid			fc_trigoid;
	HeapTuple	fc_tuple;
	Datum		fc_values[Natts_pg_trigger];
	bool		fc_nulls[Natts_pg_trigger];
	NameData	fc_evtnamedata,
				fc_evteventdata;
	ObjectAddress fc_myself,
				fc_referenced;

	/* 打开 pg_event_trigger。 */
	fc_tgrel = table_open(EventTriggerRelationId, RowExclusiveLock);

	/* 构建新的 pg_trigger 元组。 */
	fc_trigoid = GetNewOidWithIndex(fc_tgrel, EventTriggerOidIndexId,
								 Anum_pg_event_trigger_oid);
	fc_values[Anum_pg_event_trigger_oid - 1] = ObjectIdGetDatum(fc_trigoid);
	memset(fc_nulls, false, sizeof(fc_nulls));
	namestrcpy(&fc_evtnamedata, fc_trigname);
	fc_values[Anum_pg_event_trigger_evtname - 1] = NameGetDatum(&fc_evtnamedata);
	namestrcpy(&fc_evteventdata, fc_eventname);
	fc_values[Anum_pg_event_trigger_evtevent - 1] = NameGetDatum(&fc_evteventdata);
	fc_values[Anum_pg_event_trigger_evtowner - 1] = ObjectIdGetDatum(fc_evtOwner);
	fc_values[Anum_pg_event_trigger_evtfoid - 1] = ObjectIdGetDatum(fc_funcoid);
	fc_values[Anum_pg_event_trigger_evtenabled - 1] =
		CharGetDatum(TRIGGER_FIRES_ON_ORIGIN);
	if (fc_taglist == NIL)
		fc_nulls[Anum_pg_event_trigger_evttags - 1] = true;
	else
		fc_values[Anum_pg_event_trigger_evttags - 1] =
			fc_filter_list_to_array(fc_taglist);

	/* 插入堆元组。 */
	fc_tuple = heap_form_tuple(fc_tgrel->rd_att, fc_values, fc_nulls);
	CatalogTupleInsert(fc_tgrel, fc_tuple);
	heap_freetuple(fc_tuple);

	/* 依赖于所有者。 */
	recordDependencyOnOwner(EventTriggerRelationId, fc_trigoid, fc_evtOwner);

	/* 依赖于事件触发函数。 */
	fc_myself.classId = EventTriggerRelationId;
	fc_myself.objectId = fc_trigoid;
	fc_myself.objectSubId = 0;
	fc_referenced.classId = ProcedureRelationId;
	fc_referenced.objectId = fc_funcoid;
	fc_referenced.objectSubId = 0;
	recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_NORMAL);

	/* 依赖于扩展（如果有的话）。 */
	recordDependencyOnCurrentExtension(&fc_myself, false);

	/* 新事件触发器的创建后钩子 */
	InvokeObjectPostCreateHook(EventTriggerRelationId, fc_trigoid, 0);

	/* 关闭 pg_event_trigger。 */
	table_close(fc_tgrel, RowExclusiveLock);

	return fc_trigoid;
}

/*
 * 在解析器中，像 WHEN tag IN ('cmd1', 'cmd2') 的子句由一个
 * DefElem 表示，它的值是一个字符串节点的列表；在目录中，我们
 * 将字符串列表存储为文本数组。此函数将前者转换为后者。
 *
 * 为了保持整洁，我们将命令标签作为文本存储在目录中。可能
 * （尽管当前未预期）我们将来会有区分大小写的过滤变量，
 * 在这种情况下需要进一步调整。
 */
static Datum fc_filter_list_to_array(List *fc_filterlist)
{
	ListCell   *fc_lc;
	Datum	   *fc_data;
	int			fc_i = 0,
				fc_l = list_length(fc_filterlist);

	fc_data = (Datum *) palloc(fc_l * sizeof(Datum));

	foreach(fc_lc, fc_filterlist)
	{
		const char *fc_value = strVal(lfirst(fc_lc));
		char	   *fc_result,
				   *fc_p;

		fc_result = pstrdup(fc_value);
		for (fc_p = fc_result; *fc_p; fc_p++)
			*fc_p = pg_ascii_toupper((unsigned char) *fc_p);
		fc_data[fc_i++] = PointerGetDatum(cstring_to_text(fc_result));
		pfree(fc_result);
	}

	return PointerGetDatum(construct_array(fc_data, fc_l, TEXTOID,
										   -1, false, TYPALIGN_INT));
}

/*
 * ALTER EVENT TRIGGER foo ENABLE|DISABLE|ENABLE ALWAYS|REPLICA
 */
Oid AlterEventTrigger(AlterEventTrigStmt *fc_stmt)
{
	Relation	fc_tgrel;
	HeapTuple	fc_tup;
	Oid			fc_trigoid;
	Form_pg_event_trigger fc_evtForm;
	char		fc_tgenabled = fc_stmt->tgenabled;

	fc_tgrel = table_open(EventTriggerRelationId, RowExclusiveLock);

	fc_tup = SearchSysCacheCopy1(EVENTTRIGGERNAME,
							  CStringGetDatum(fc_stmt->trigname));
	if (!HeapTupleIsValid(fc_tup))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("event trigger \"%s\" does not exist",
						fc_stmt->trigname)));

	fc_evtForm = (Form_pg_event_trigger) GETSTRUCT(fc_tup);
	fc_trigoid = fc_evtForm->oid;

	if (!pg_event_trigger_ownercheck(fc_trigoid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EVENT_TRIGGER,
					   fc_stmt->trigname);

	/* 元组是一个副本，因此我们可以在下面修改它 */
	fc_evtForm->evtenabled = fc_tgenabled;

	CatalogTupleUpdate(fc_tgrel, &fc_tup->t_self, fc_tup);

	InvokeObjectPostAlterHook(EventTriggerRelationId,
							  fc_trigoid, 0);

	/* 清理 */
	heap_freetuple(fc_tup);
	table_close(fc_tgrel, RowExclusiveLock);

	return fc_trigoid;
}

/*
 * 按名称更改事件触发器的所有者
 */
ObjectAddress AlterEventTriggerOwner(const char *fc_name, Oid fc_newOwnerId)
{
	Oid			fc_evtOid;
	HeapTuple	fc_tup;
	Form_pg_event_trigger fc_evtForm;
	Relation	fc_rel;
	ObjectAddress fc_address;

	fc_rel = table_open(EventTriggerRelationId, RowExclusiveLock);

	fc_tup = SearchSysCacheCopy1(EVENTTRIGGERNAME, CStringGetDatum(fc_name));

	if (!HeapTupleIsValid(fc_tup))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("event trigger \"%s\" does not exist", fc_name)));

	fc_evtForm = (Form_pg_event_trigger) GETSTRUCT(fc_tup);
	fc_evtOid = fc_evtForm->oid;

	fc_AlterEventTriggerOwner_internal(fc_rel, fc_tup, fc_newOwnerId);

	ObjectAddressSet(fc_address, EventTriggerRelationId, fc_evtOid);

	heap_freetuple(fc_tup);

	table_close(fc_rel, RowExclusiveLock);

	return fc_address;
}

/*
 * 按 OID 更改事件触发器的所有者
 */
void AlterEventTriggerOwner_oid(Oid fc_trigOid, Oid fc_newOwnerId)
{
	HeapTuple	fc_tup;
	Relation	fc_rel;

	fc_rel = table_open(EventTriggerRelationId, RowExclusiveLock);

	fc_tup = SearchSysCacheCopy1(EVENTTRIGGEROID, ObjectIdGetDatum(fc_trigOid));

	if (!HeapTupleIsValid(fc_tup))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("event trigger with OID %u does not exist", fc_trigOid)));

	fc_AlterEventTriggerOwner_internal(fc_rel, fc_tup, fc_newOwnerId);

	heap_freetuple(fc_tup);

	table_close(fc_rel, RowExclusiveLock);
}

/*
 * 更改事件触发器所有者的内部工作程序
 */
static void fc_AlterEventTriggerOwner_internal(Relation fc_rel, HeapTuple fc_tup, Oid fc_newOwnerId)
{
	Form_pg_event_trigger fc_form;

	fc_form = (Form_pg_event_trigger) GETSTRUCT(fc_tup);

	if (fc_form->evtowner == fc_newOwnerId)
		return;

	if (!pg_event_trigger_ownercheck(fc_form->oid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EVENT_TRIGGER,
					   NameStr(fc_form->evtname));

	/* 新所有者必须是超级用户 */
	if (!superuser_arg(fc_newOwnerId))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied to change owner of event trigger \"%s\"",
						NameStr(fc_form->evtname)),
				 errhint("The owner of an event trigger must be a superuser.")));

	fc_form->evtowner = fc_newOwnerId;
	CatalogTupleUpdate(fc_rel, &fc_tup->t_self, fc_tup);

	/* 更新所有者依赖关系引用 */
	changeDependencyOnOwner(EventTriggerRelationId,
							fc_form->oid,
							fc_newOwnerId);

	InvokeObjectPostAlterHook(EventTriggerRelationId,
							  fc_form->oid, 0);
}

/*
 * get_event_trigger_oid - 通过名称查找事件触发器以找到其 OID。
 *
 * 如果 missing_ok 为 false，当没有找到触发器时抛出错误。
 * 如果为 true，只返回 InvalidOid。
 */
Oid get_event_trigger_oid(const char *fc_trigname, bool fc_missing_ok)
{
	Oid			fc_oid;

	fc_oid = GetSysCacheOid1(EVENTTRIGGERNAME, Anum_pg_event_trigger_oid,
						  CStringGetDatum(fc_trigname));
	if (!OidIsValid(fc_oid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("event trigger \"%s\" does not exist", fc_trigname)));
	return fc_oid;
}

/*
 * 当我们希望触发给定事件触发器并且其他情况下返回 false 时，
 * 过滤会话复制角色和已注册事件触发器标签的匹配。
 */
static bool fc_filter_event_trigger(CommandTag fc_tag, EventTriggerCacheItem *fc_item)
{
	/*
	 * 按会话复制角色过滤，知道我们在这里永远不会见到被禁用的
	 * 项目。
	 */
	if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
	{
		if (fc_item->enabled == TRIGGER_FIRES_ON_ORIGIN)
			return false;
	}
	else
	{
		if (fc_item->enabled == TRIGGER_FIRES_ON_REPLICA)
			return false;
	}

	/* 按标签过滤，如果有指定的话。 */
	if (!bms_is_empty(fc_item->tagset) && !bms_is_member(fc_tag, fc_item->tagset))
		return false;

	/* 如果我们到达那一点，我们就不会过滤掉这个项。 */
	return true;
}

/*
 * 为给定事件的触发器运行设置。返回值是一个OID列表
 * 的函数要运行；如果有，trigdata将被填充为适当的
 * EventTriggerData，以便它们接收。
 */
static List * fc_EventTriggerCommonSetup(Node *fc_parsetree,
						EventTriggerEvent fc_event, const char *fc_eventstr,
						EventTriggerData *fc_trigdata)
{
	CommandTag	fc_tag;
	List	   *fc_cachelist;
	ListCell   *fc_lc;
	List	   *fc_runlist = NIL;

	/*
	 * 我们希望此过程实际调用的命令标签列表与
	 * CREATE EVENT TRIGGER 接受的列表完全匹配。
	 * 这个调试校对将抛出错误，如果这个
	 * 函数被调用用于 CREATE EVENT TRIGGER 不接受的命令标签。
	 * （不幸的是，似乎没有简单的自动化
	 * 方法来验证 CREATE EVENT TRIGGER 不接受从未到达此控制点的额外内容。）
	 *
	 * 如果这个交叉检查对你失败，你可能需要调整
	 * standard_ProcessUtility() 以不调用事件触发器
	 * 针对相关的命令类型，或者你需要调整
	 * event_trigger_ok 以接受相关的命令标签。
	 */
#ifdef USE_ASSERT_CHECKING
	{
		CommandTag	fc_dbgtag;

		fc_dbgtag = CreateCommandTag(fc_parsetree);
		if (fc_event == EVT_DDLCommandStart ||
			fc_event == EVT_DDLCommandEnd ||
			fc_event == EVT_SQLDrop)
		{
			if (!command_tag_event_trigger_ok(fc_dbgtag))
				elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(fc_dbgtag));
		}
		else if (fc_event == EVT_TableRewrite)
		{
			if (!command_tag_table_rewrite_ok(fc_dbgtag))
				elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(fc_dbgtag));
		}
	}
#endif

	/* 使用缓存查找此事件的触发器；如果没有，则快速退出。 */
	fc_cachelist = EventCacheLookup(fc_event);
	if (fc_cachelist == NIL)
		return NIL;

	/* 获取命令标签。 */
	fc_tag = CreateCommandTag(fc_parsetree);

	/*
	 * 按命令标签过滤事件触发器列表，并将它们复制到我们的
	 * 内存上下文中。一旦我们开始运行命令触发器，或者实际上
	 * 一旦我们做任何涉及目录的事情，无效化
	 * 可能会让缓存列表指向垃圾，因此我们必须在
	 * 做其他任何事情之前完成这个。
	 */
	foreach(fc_lc, fc_cachelist)
	{
		EventTriggerCacheItem *fc_item = lfirst(fc_lc);

		if (fc_filter_event_trigger(fc_tag, fc_item))
		{
			/* 我们必须计划触发此触发器。 */
			fc_runlist = lappend_oid(fc_runlist, fc_item->fnoid);
		}
	}

	/* 如果没有要运行的函数，就不要再花时间在这上面。 */
	if (fc_runlist == NIL)
		return NIL;

	fc_trigdata->type = T_EventTriggerData;
	fc_trigdata->event = fc_eventstr;
	fc_trigdata->parsetree = fc_parsetree;
	fc_trigdata->tag = fc_tag;

	return fc_runlist;
}

/*
 * 触发 ddl_command_start 触发器。
 */
void EventTriggerDDLCommandStart(Node *fc_parsetree)
{
	List	   *fc_runlist;
	EventTriggerData fc_trigdata;

	/*
	 * 事件触发器在独立模式下完全禁用。对此有
	 * （至少）两个原因：
	 *
	 * 1. 一个严重损坏的事件触发器可能不仅会使
	 * 数据库无法使用，还可能阻止自身的禁用以修复状况。
	 * 在这种情况下，在独立模式下重启提供了一个逃生途径。
	 *
	 * 2. BuildEventTriggerCache 依赖于 systable_beginscan_ordered，因此
	 * 如果 pg_event_trigger 的索引损坏，则会出现故障。
	 * 为了允许从损坏的索引恢复，我们需要某种操作模式
	 * 其中事件触发器被禁用。（或者我们可以为这种情况实现
	 * heapscan-and-sort 逻辑，但让灾难恢复
	 * 场景依赖于其他未经过测试的代码并不令人愉快。）
	 */
	if (!IsUnderPostmaster)
		return;

	fc_runlist = fc_EventTriggerCommonSetup(fc_parsetree,
									  EVT_DDLCommandStart,
									  "ddl_command_start",
									  &fc_trigdata);
	if (fc_runlist == NIL)
		return;

	/* 运行触发器。 */
	fc_EventTriggerInvoke(fc_runlist, &fc_trigdata);

	/* 清理。 */
	list_free(fc_runlist);

	/*
	 * 确保事件触发器所做的任何事情对主
	 * 命令是可见的。
	 */
	CommandCounterIncrement();
}

/*
 * 触发 ddl_command_end 触发器。
 */
void EventTriggerDDLCommandEnd(Node *fc_parsetree)
{
	List	   *fc_runlist;
	EventTriggerData fc_trigdata;

	/*
	 * 有关事件触发器在单用户模式下禁用的原因，请参见
	 * EventTriggerDDLCommandStart 的讨论。
	 */
	if (!IsUnderPostmaster)
		return;

	/*
	 * 如果我们的状态没有设置，也不做任何事情，如果在当前 DDL
	 * 命令开始时没有任何相关事件触发器的话。 因此，这个测试可能看起来是可选的，但它是
	 * *必要的*，因为 EventTriggerCommonSetup 可能会发现命令开始时不存在的触发器。
	 */
	if (!currentEventTriggerState)
		return;

	fc_runlist = fc_EventTriggerCommonSetup(fc_parsetree,
									  EVT_DDLCommandEnd, "ddl_command_end",
									  &fc_trigdata);
	if (fc_runlist == NIL)
		return;

	/*
	 * 确保主命令所做的任何事情对于事件触发器是可见的。
	 */
	CommandCounterIncrement();

	/* 运行触发器。 */
	fc_EventTriggerInvoke(fc_runlist, &fc_trigdata);

	/* 清理。 */
	list_free(fc_runlist);
}

/*
 * 触发 sql_drop 触发器。
 */
void EventTriggerSQLDrop(Node *fc_parsetree)
{
	List	   *fc_runlist;
	EventTriggerData fc_trigdata;

	/*
	 * 有关事件触发器在单用户模式下禁用的原因，请参见
	 * EventTriggerDDLCommandStart 的讨论。
	 */
	if (!IsUnderPostmaster)
		return;

	/*
	 * 使用当前状态来确定此事件是否根本触发。如果没有
	 * sql_drop 事件的触发器，那么我们在这里没有任何事情可做。 请注意，如果是这种情况，丢弃对象的收集是禁用的，
	 * 所以即使我们尝试运行，列表也会是空的。
	 */
	if (!currentEventTriggerState ||
		slist_is_empty(&currentEventTriggerState->SQLDropList))
		return;

	fc_runlist = fc_EventTriggerCommonSetup(fc_parsetree,
									  EVT_SQLDrop, "sql_drop",
									  &fc_trigdata);

	/*
	 * 如果运行列表为空，则没有工作要做。 注意这通常不会发生，
	 * 因为如果没有 sql_drop 事件，那么要丢弃的对象在第一
	 * 便不会被收集，我们会在上面退出。
	 * 但如果事件触发器在中途被丢弃，则可能会发生这种情况。
	 */
	if (fc_runlist == NIL)
		return;

	/*
	 * 确保主命令所做的任何事情对于事件触发器是可见的。
	 */
	CommandCounterIncrement();

	/*
	 * 确保 pg_event_trigger_dropped_objects 仅在运行这些触发器时有效。
	 * 使用 PG_TRY 确保即使一个触发器失败，in_sql_drop 也会被重置。
	 * （这可能不是必要的，因为 currentState 变量将很快被我们的调用者移除，
	 * 但似乎保持安全更好。）
	 */
	currentEventTriggerState->in_sql_drop = true;

	/* 运行触发器。 */
	PG_TRY();
	{
		fc_EventTriggerInvoke(fc_runlist, &fc_trigdata);
	}
	PG_FINALLY();
	{
		currentEventTriggerState->in_sql_drop = false;
	}
	PG_END_TRY();

	/* 清理。 */
	list_free(fc_runlist);
}


/*
 * 触发 table_rewrite 触发器。
 */
void EventTriggerTableRewrite(Node *fc_parsetree, Oid fc_tableOid, int fc_reason)
{
	List	   *fc_runlist;
	EventTriggerData fc_trigdata;

	/*
	 * 有关事件触发器在单用户模式下禁用的原因，请参见
	 * EventTriggerDDLCommandStart 的讨论。
	 */
	if (!IsUnderPostmaster)
		return;

	/*
	 * 如果我们的状态没有设置，也不做任何事情，如果在当前 DDL
	 * 命令开始时没有任何相关事件触发器的话。  因此，这个测试可能看起来是可选的，但它是
	 * *必要的*，因为 EventTriggerCommonSetup 可能会发现命令开始时不存在的触发器。
	 */
	if (!currentEventTriggerState)
		return;

	fc_runlist = fc_EventTriggerCommonSetup(fc_parsetree,
									  EVT_TableRewrite,
									  "table_rewrite",
									  &fc_trigdata);
	if (fc_runlist == NIL)
		return;

	/*
	 * 确保 pg_event_trigger_table_rewrite_oid 仅在运行这些触发器时有效。
	 * 使用 PG_TRY 确保 table_rewrite_oid 即使在一个触发器失败时也会被重置。
	 * （这可能不是必要的，因为 currentState 变量将很快被我们的调用者移除，
	 * 但似乎保持安全更好。）
	 */
	currentEventTriggerState->table_rewrite_oid = fc_tableOid;
	currentEventTriggerState->table_rewrite_reason = fc_reason;

	/* 运行触发器。 */
	PG_TRY();
	{
		fc_EventTriggerInvoke(fc_runlist, &fc_trigdata);
	}
	PG_FINALLY();
	{
		currentEventTriggerState->table_rewrite_oid = InvalidOid;
		currentEventTriggerState->table_rewrite_reason = 0;
	}
	PG_END_TRY();

	/* 清理。 */
	list_free(fc_runlist);

	/*
	 * 确保事件触发器所做的任何事情对主
	 * 命令是可见的。
	 */
	CommandCounterIncrement();
}

/*
 * 调用事件触发器列表中的每一个事件触发器。
 */
static void fc_EventTriggerInvoke(List *fc_fn_oid_list, EventTriggerData *fc_trigdata)
{
	MemoryContext fc_context;
	MemoryContext fc_oldcontext;
	ListCell   *fc_lc;
	bool		fc_first = true;

	/* 防止因递归事件触发器导致的栈溢出 */
	check_stack_depth();

	/*
	 * 让我们在自己的内存上下文中评估事件触发器，以便任何
	 * 内存泄漏能够迅速清理。
	 */
	fc_context = AllocSetContextCreate(CurrentMemoryContext,
									"event trigger context",
									ALLOCSET_DEFAULT_SIZES);
	fc_oldcontext = MemoryContextSwitchTo(fc_context);

	/* 调用每个事件触发器。 */
	foreach(fc_lc, fc_fn_oid_list)
	{
		LOCAL_FCINFO(fc_fcinfo, 0);
		Oid			fc_fnoid = lfirst_oid(fc_lc);
		FmgrInfo	fc_flinfo;
		PgStat_FunctionCallUsage fc_fcusage;

		elog(DEBUG1, "EventTriggerInvoke %u", fc_fnoid);

		/*
		 * 我们希望每个事件触发器能够看到前一个事件触发器
		 * 操作的结果。调用者负责在事件触发器和事务中的其他任何
		 * 事务之间所需的命令计数器递增。
		 */
		if (fc_first)
			fc_first = false;
		else
			CommandCounterIncrement();

		/* 查找函数 */
		fmgr_info(fc_fnoid, &fc_flinfo);

		/* 调用函数，不传递参数，但设置上下文。 */
		InitFunctionCallInfoData(*fc_fcinfo, &fc_flinfo, 0,
								 InvalidOid, (Node *) fc_trigdata, NULL);
		pgstat_init_function_usage(fc_fcinfo, &fc_fcusage);
		FunctionCallInvoke(fc_fcinfo);
		pgstat_end_function_usage(&fc_fcusage, true);

		/* 回收内存。 */
		MemoryContextReset(fc_context);
	}

	/* 恢复旧的内存上下文并删除临时上下文。 */
	MemoryContextSwitchTo(fc_oldcontext);
	MemoryContextDelete(fc_context);
}

/*
 * 事件触发器支持此对象类型吗？
 */
bool EventTriggerSupportsObjectType(ObjectType fc_obtype)
{
	switch (fc_obtype)
	{
		case OBJECT_DATABASE:
		case OBJECT_TABLESPACE:
		case OBJECT_ROLE:
		case OBJECT_PARAMETER_ACL:
			/* 不支持全局对象 */
			return false;
		case OBJECT_EVENT_TRIGGER:
			/* 不支持事件触发器上的事件触发器 */
			return false;
		case OBJECT_ACCESS_METHOD:
		case OBJECT_AGGREGATE:
		case OBJECT_AMOP:
		case OBJECT_AMPROC:
		case OBJECT_ATTRIBUTE:
		case OBJECT_CAST:
		case OBJECT_COLUMN:
		case OBJECT_COLLATION:
		case OBJECT_CONVERSION:
		case OBJECT_DEFACL:
		case OBJECT_DEFAULT:
		case OBJECT_DOMAIN:
		case OBJECT_DOMCONSTRAINT:
		case OBJECT_EXTENSION:
		case OBJECT_FDW:
		case OBJECT_FOREIGN_SERVER:
		case OBJECT_FOREIGN_TABLE:
		case OBJECT_FUNCTION:
		case OBJECT_INDEX:
		case OBJECT_LANGUAGE:
		case OBJECT_LARGEOBJECT:
		case OBJECT_MATVIEW:
		case OBJECT_OPCLASS:
		case OBJECT_OPERATOR:
		case OBJECT_OPFAMILY:
		case OBJECT_POLICY:
		case OBJECT_PROCEDURE:
		case OBJECT_PUBLICATION:
		case OBJECT_PUBLICATION_NAMESPACE:
		case OBJECT_PUBLICATION_REL:
		case OBJECT_ROUTINE:
		case OBJECT_RULE:
		case OBJECT_SCHEMA:
		case OBJECT_SEQUENCE:
		case OBJECT_SUBSCRIPTION:
		case OBJECT_STATISTIC_EXT:
		case OBJECT_TABCONSTRAINT:
		case OBJECT_TABLE:
		case OBJECT_TRANSFORM:
		case OBJECT_TRIGGER:
		case OBJECT_TSCONFIGURATION:
		case OBJECT_TSDICTIONARY:
		case OBJECT_TSPARSER:
		case OBJECT_TSTEMPLATE:
		case OBJECT_TYPE:
		case OBJECT_USER_MAPPING:
		case OBJECT_VIEW:
			return true;

			/*
			 * 这里故意没有 default: 情况；我们希望
			 * 编译器在上面没有处理新的 ObjectType 时发出警告。
			 */
	}

	/* 不应该到这里，但如果到了，就说“没有支持” */
	return false;
}

/*
 * 事件触发器支持此对象类吗？
 */
bool EventTriggerSupportsObjectClass(ObjectClass fc_objclass)
{
	switch (fc_objclass)
	{
		case OCLASS_DATABASE:
		case OCLASS_TBLSPACE:
		case OCLASS_ROLE:
		case OCLASS_PARAMETER_ACL:
			/* 不支持全局对象 */
			return false;
		case OCLASS_EVENT_TRIGGER:
			/* 不支持事件触发器上的事件触发器 */
			return false;
		case OCLASS_CLASS:
		case OCLASS_PROC:
		case OCLASS_TYPE:
		case OCLASS_CAST:
		case OCLASS_COLLATION:
		case OCLASS_CONSTRAINT:
		case OCLASS_CONVERSION:
		case OCLASS_DEFAULT:
		case OCLASS_LANGUAGE:
		case OCLASS_LARGEOBJECT:
		case OCLASS_OPERATOR:
		case OCLASS_OPCLASS:
		case OCLASS_OPFAMILY:
		case OCLASS_AM:
		case OCLASS_AMOP:
		case OCLASS_AMPROC:
		case OCLASS_REWRITE:
		case OCLASS_TRIGGER:
		case OCLASS_SCHEMA:
		case OCLASS_STATISTIC_EXT:
		case OCLASS_TSPARSER:
		case OCLASS_TSDICT:
		case OCLASS_TSTEMPLATE:
		case OCLASS_TSCONFIG:
		case OCLASS_FDW:
		case OCLASS_FOREIGN_SERVER:
		case OCLASS_USER_MAPPING:
		case OCLASS_DEFACL:
		case OCLASS_EXTENSION:
		case OCLASS_POLICY:
		case OCLASS_PUBLICATION:
		case OCLASS_PUBLICATION_NAMESPACE:
		case OCLASS_PUBLICATION_REL:
		case OCLASS_SUBSCRIPTION:
		case OCLASS_TRANSFORM:
			return true;

			/*
			 * 这里故意没有默认 case；我们希望编译器
			 * 在上面未处理新 OCLASS 时发出警告。
			 */
	}

	/* 不应该到这里，但如果到了，就说“没有支持” */
	return false;
}

/*
 * 为新完整查询准备事件触发器状态（如果必要的话）；返回这是否
 * 完成。如果已经完成，当查询结束后必须调用 EventTriggerEndCompleteQuery，
 * 无论其成功还是失败 -- 因此必须使用 PG_TRY 块。
 */
bool EventTriggerBeginCompleteQuery(void)
{
	EventTriggerQueryState *fc_state;
	MemoryContext fc_cxt;

	/*
	 * 目前，sql_drop、table_rewrite、ddl_command_end 事件是唯一
	 * 有理由保留事件触发器状态的原因；所以如果没有，便不安装一个。
	 */
	if (!trackDroppedObjectsNeeded())
		return false;

	fc_cxt = AllocSetContextCreate(TopMemoryContext,
								"event trigger state",
								ALLOCSET_DEFAULT_SIZES);
	fc_state = MemoryContextAlloc(fc_cxt, sizeof(EventTriggerQueryState));
	fc_state->cxt = fc_cxt;
	slist_init(&(fc_state->SQLDropList));
	fc_state->in_sql_drop = false;
	fc_state->table_rewrite_oid = InvalidOid;

	fc_state->commandCollectionInhibited = currentEventTriggerState ?
		currentEventTriggerState->commandCollectionInhibited : false;
	fc_state->currentCommand = NULL;
	fc_state->commandList = NIL;
	fc_state->previous = currentEventTriggerState;
	currentEventTriggerState = fc_state;

	return true;
}

/*
 * 查询完成（或出现错误） -- 清理本地状态，返回到之前的状态。
 *
 * 注意：如果 EventTriggerBeginCompleteQuery 之前返回 false，调用
 * 此例程是错误的。
 *
 * 注意：这可能在失败事务的 PG_CATCH 块中被调用，
 * 因此要小心运行任何不必要的操作。 （尤其是，可能
 * 不明智去尝试分配内存。）
 */
void EventTriggerEndCompleteQuery(void)
{
	EventTriggerQueryState *fc_prevstate;

	fc_prevstate = currentEventTriggerState->previous;

	/* 这避免了对 SQLDropList 项的逐个 pfree 的需求： */
	MemoryContextDelete(currentEventTriggerState->cxt);

	currentEventTriggerState = fc_prevstate;
}

/*
 * 我们是否需要密切关注被删除的对象？
 *
 * 这很有用，因为启用它们运行是有成本的。
 */
bool trackDroppedObjectsNeeded(void)
{
	/*
	 * 如果存在任何 sql_drop、table_rewrite、ddl_command_end 事件触发器，
	 * 则为 true
	 */
	return list_length(EventCacheLookup(EVT_SQLDrop)) > 0 ||
		list_length(EventCacheLookup(EVT_TableRewrite)) > 0 ||
		list_length(EventCacheLookup(EVT_DDLCommandEnd)) > 0;
}

/*
 * 支持在事件触发函数中丢弃对象的信息。
 *
 * 我们将当前命令丢弃的对象列表保存在当前状态的 SQLDropList 中（由 SQLDropObject 项组成）。每当一个新命令要开始时，都会创建一个干净的 EventTriggerQueryState；丢弃对象的命令会进行 dependency.c 的舞蹈来丢弃对象，这会填充当前状态的 SQLDropList；当事件触发器被调用时，它们可以通过 pg_event_trigger_dropped_objects() 来获取该列表。当命令完成时，EventTriggerQueryState 会被清空，之前命令的状态会被恢复（当没有命令在执行时，当前状态为 NULL）。
 *
 * 所有这些使我们能够支持事件触发函数“重入”地丢弃对象的情况。
 */

/*
 * 将一个对象注册为当前命令丢弃的对象。
 */
void EventTriggerSQLDropAddObject(const ObjectAddress *fc_object, bool fc_original, bool fc_normal)
{
	SQLDropObject *fc_obj;
	MemoryContext fc_oldcxt;

	if (!currentEventTriggerState)
		return;

	Assert(EventTriggerSupportsObjectClass(getObjectClass(fc_object)));

	/* 不报告临时模式除我的以外 */
	if (fc_object->classId == NamespaceRelationId &&
		(isAnyTempNamespace(fc_object->objectId) &&
		 !isTempNamespace(fc_object->objectId)))
		return;

	fc_oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);

	fc_obj = palloc0(sizeof(SQLDropObject));
	fc_obj->address = *fc_object;
	fc_obj->original = fc_original;
	fc_obj->normal = fc_normal;

	/*
	 * 从对象的目录元组中获取模式名称（如果存在的话）；
	 * 这使我们可以跳过临时模式中的对象。我们相信
	 * ObjectProperty 包含所有可以
	 * 具有模式限定的对象类。
	 */
	if (is_objectclass_supported(fc_object->classId))
	{
		Relation	fc_catalog;
		HeapTuple	fc_tuple;

		fc_catalog = table_open(fc_obj->address.classId, AccessShareLock);
		fc_tuple = get_catalog_object_by_oid(fc_catalog,
										  get_object_attnum_oid(fc_object->classId),
										  fc_obj->address.objectId);

		if (fc_tuple)
		{
			AttrNumber	fc_attnum;
			Datum		fc_datum;
			bool		fc_isnull;

			fc_attnum = get_object_attnum_namespace(fc_obj->address.classId);
			if (fc_attnum != InvalidAttrNumber)
			{
				fc_datum = heap_getattr(fc_tuple, fc_attnum,
									 RelationGetDescr(fc_catalog), &fc_isnull);
				if (!fc_isnull)
				{
					Oid			fc_namespaceId;

					fc_namespaceId = DatumGetObjectId(fc_datum);
					/* 只有在它们是我自己的情况下才报告临时对象 */
					if (isTempNamespace(fc_namespaceId))
					{
						fc_obj->schemaname = "pg_temp";
						fc_obj->istemp = true;
					}
					else if (isAnyTempNamespace(fc_namespaceId))
					{
						pfree(fc_obj);
						table_close(fc_catalog, AccessShareLock);
						MemoryContextSwitchTo(fc_oldcxt);
						return;
					}
					else
					{
						fc_obj->schemaname = get_namespace_name(fc_namespaceId);
						fc_obj->istemp = false;
					}
				}
			}

			if (get_object_namensp_unique(fc_obj->address.classId) &&
				fc_obj->address.objectSubId == 0)
			{
				fc_attnum = get_object_attnum_name(fc_obj->address.classId);
				if (fc_attnum != InvalidAttrNumber)
				{
					fc_datum = heap_getattr(fc_tuple, fc_attnum,
										 RelationGetDescr(fc_catalog), &fc_isnull);
					if (!fc_isnull)
						fc_obj->objname = pstrdup(NameStr(*DatumGetName(fc_datum)));
				}
			}
		}

		table_close(fc_catalog, AccessShareLock);
	}
	else
	{
		if (fc_object->classId == NamespaceRelationId &&
			isTempNamespace(fc_object->objectId))
			fc_obj->istemp = true;
	}

	/* 对象标识，objname 和 objargs */
	fc_obj->objidentity =
		getObjectIdentityParts(&fc_obj->address, &fc_obj->addrnames, &fc_obj->addrargs,
							   false);

	/* 对象类型 */
	fc_obj->objecttype = getObjectTypeDescription(&fc_obj->address, false);

	slist_push_head(&(currentEventTriggerState->SQLDropList), &fc_obj->next);

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * pg_event_trigger_dropped_objects
 *
 * 将丢弃对象的列表提供给事件触发器运行的用户函数。
 */
Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
{
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	slist_iter	fc_iter;

	/*
	 * 保护此函数不在上下文外被调用
	 */
	if (!currentEventTriggerState ||
		!currentEventTriggerState->in_sql_drop)
		ereport(ERROR,
				(errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED),
				 errmsg("%s can only be called in a sql_drop event trigger function",
						"pg_event_trigger_dropped_objects()")));

	/* 构建元组存储以保存结果行 */
	InitMaterializedSRF(fcinfo, 0);

	slist_foreach(fc_iter, &(currentEventTriggerState->SQLDropList))
	{
		SQLDropObject *fc_obj;
		int			fc_i = 0;
		Datum		fc_values[12];
		bool		fc_nulls[12];

		fc_obj = slist_container(SQLDropObject, next, fc_iter.cur);

		MemSet(fc_values, 0, sizeof(fc_values));
		MemSet(fc_nulls, 0, sizeof(fc_nulls));

		/* classid */
		fc_values[fc_i++] = ObjectIdGetDatum(fc_obj->address.classId);

		/* objid */
		fc_values[fc_i++] = ObjectIdGetDatum(fc_obj->address.objectId);

		/* objsubid */
		fc_values[fc_i++] = Int32GetDatum(fc_obj->address.objectSubId);

		/* original */
		fc_values[fc_i++] = BoolGetDatum(fc_obj->original);

		/* normal */
		fc_values[fc_i++] = BoolGetDatum(fc_obj->normal);

		/* is_temporary */
		fc_values[fc_i++] = BoolGetDatum(fc_obj->istemp);

		/* object_type */
		fc_values[fc_i++] = CStringGetTextDatum(fc_obj->objecttype);

		/* schema_name */
		if (fc_obj->schemaname)
			fc_values[fc_i++] = CStringGetTextDatum(fc_obj->schemaname);
		else
			fc_nulls[fc_i++] = true;

		/* object_name */
		if (fc_obj->objname)
			fc_values[fc_i++] = CStringGetTextDatum(fc_obj->objname);
		else
			fc_nulls[fc_i++] = true;

		/* object_identity */
		if (fc_obj->objidentity)
			fc_values[fc_i++] = CStringGetTextDatum(fc_obj->objidentity);
		else
			fc_nulls[fc_i++] = true;

		/* address_names 和 address_args */
		if (fc_obj->addrnames)
		{
			fc_values[fc_i++] = PointerGetDatum(strlist_to_textarray(fc_obj->addrnames));

			if (fc_obj->addrargs)
				fc_values[fc_i++] = PointerGetDatum(strlist_to_textarray(fc_obj->addrargs));
			else
				fc_values[fc_i++] = PointerGetDatum(construct_empty_array(TEXTOID));
		}
		else
		{
			fc_nulls[fc_i++] = true;
			fc_nulls[fc_i++] = true;
		}

		tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc,
							 fc_values, fc_nulls);
	}

	return (Datum) 0;
}

/*
 * pg_event_trigger_table_rewrite_oid
 *
 * 将即将被重写的表的 Oid 提供给事件触发器运行的用户
 * 函数。
 */
Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS)
{
	/*
	 * 保护此函数不在上下文外被调用
	 */
	if (!currentEventTriggerState ||
		currentEventTriggerState->table_rewrite_oid == InvalidOid)
		ereport(ERROR,
				(errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED),
				 errmsg("%s can only be called in a table_rewrite event trigger function",
						"pg_event_trigger_table_rewrite_oid()")));

	PG_RETURN_OID(currentEventTriggerState->table_rewrite_oid);
}

/*
 * pg_event_trigger_table_rewrite_reason
 *
 * 将重写原因提供给用户。
 */
Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
{
	/*
	 * 保护此函数不在上下文外被调用
	 */
	if (!currentEventTriggerState ||
		currentEventTriggerState->table_rewrite_reason == 0)
		ereport(ERROR,
				(errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED),
				 errmsg("%s can only be called in a table_rewrite event trigger function",
						"pg_event_trigger_table_rewrite_reason()")));

	PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
}

/*-------------------------------------------------------------------------
 * 支持 DDL 命令反解析
 *
 * 下面的例程使事件触发函数能够在 DDL 命令执行时获取命令列表。
 * 此功能主要有三部分：
 *
 * 1) 在 ProcessUtilitySlow 或其某个子例程中，每个 DDL 命令
 * 使用下面的例程将自身添加为结构体 CollectedCommand 的表示形式到命令列表中。
 *
 * 2) 在某个时间之后，ddl_command_end 被触发，命令列表通过
 * pg_event_trigger_ddl_commands() 可供事件触发函数使用；
 * 完整的命令细节作为 pg_ddl_command 类型的列暴露出来。
 *
 * 3) 扩展可以安装一个能够接受 pg_ddl_command 类型值的函数，
 * 并将其转化为某种外部的、用户可见的和/或可修改的表示形式。
 *-------------------------------------------------------------------------
 */

/*
 * 禁止 DDL 命令收集。
 */
void EventTriggerInhibitCommandCollection(void)
{
	if (!currentEventTriggerState)
		return;

	currentEventTriggerState->commandCollectionInhibited = true;
}

/*
 * 重新建立 DDL 命令收集。
 */
void EventTriggerUndoInhibitCommandCollection(void)
{
	if (!currentEventTriggerState)
		return;

	currentEventTriggerState->commandCollectionInhibited = false;
}

/*
 * EventTriggerCollectSimpleCommand
 *		保存刚刚执行的简单 DDL 命令的数据
 *
 * address 标识正在操作的对象。secondaryObject 是与执行的命令
 * 以某种方式相关的对象地址；其含义是特定于命令的。
 *
 * 例如，对于 ALTER obj SET SCHEMA 命令，objtype 是正在移动的对象
 * 的类型，objectId 是其 OID，secondaryOid 是旧模式的 OID。
 * （目标模式 OID 可以通过对对象的目录查找获得。）
 */
void EventTriggerCollectSimpleCommand(ObjectAddress fc_address,
								 ObjectAddress fc_secondaryObject,
								 Node *fc_parsetree)
{
	MemoryContext fc_oldcxt;
	CollectedCommand *fc_command;

	/* 如果事件触发上下文未设置或收集被禁用，则忽略 */
	if (!currentEventTriggerState ||
		currentEventTriggerState->commandCollectionInhibited)
		return;

	fc_oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);

	fc_command = palloc(sizeof(CollectedCommand));

	fc_command->type = SCT_Simple;
	fc_command->in_extension = creating_extension;

	fc_command->d.simple.address = fc_address;
	fc_command->d.simple.secondaryObject = fc_secondaryObject;
	fc_command->parsetree = copyObject(fc_parsetree);

	currentEventTriggerState->commandList = lappend(currentEventTriggerState->commandList,
													fc_command);

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * EventTriggerAlterTableStart
 *		准备接收即将执行的 ALTER TABLE 命令的数据
 *
 * 注意我们不会立即收集命令；相反，我们将其保存在
 * currentCommand 中，只有在完成处理子命令后，才会将其
 * 添加到命令列表中。
 */
void EventTriggerAlterTableStart(Node *fc_parsetree)
{
	MemoryContext fc_oldcxt;
	CollectedCommand *fc_command;

	/* 如果事件触发上下文未设置或收集被禁用，则忽略 */
	if (!currentEventTriggerState ||
		currentEventTriggerState->commandCollectionInhibited)
		return;

	fc_oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);

	fc_command = palloc(sizeof(CollectedCommand));

	fc_command->type = SCT_AlterTable;
	fc_command->in_extension = creating_extension;

	fc_command->d.alterTable.classId = RelationRelationId;
	fc_command->d.alterTable.objectId = InvalidOid;
	fc_command->d.alterTable.subcmds = NIL;
	fc_command->parsetree = copyObject(fc_parsetree);

	fc_command->parent = currentEventTriggerState->currentCommand;
	currentEventTriggerState->currentCommand = fc_command;

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * 记住受 ALTER TABLE 影响的对象的 OID。
 *
 * 这是必要的，因为在某些情况下我们直到后来才知道 OID。
 */
void EventTriggerAlterTableRelid(Oid fc_objectId)
{
	if (!currentEventTriggerState ||
		currentEventTriggerState->commandCollectionInhibited)
		return;

	currentEventTriggerState->currentCommand->d.alterTable.objectId = fc_objectId;
}

/*
 * EventTriggerCollectAlterTableSubcmd
 *		保存关于 ALTER TABLE 的单个部分的数据。
 *
 * 几个不同的命令通过这条路径，但除了 ALTER TABLE 本身，
 * 其他所有命令都涉及到在内部生成的 AlterTableCmd 节点，
 * 所以目前这段代码仅需处理这些。
 */
void EventTriggerCollectAlterTableSubcmd(Node *fc_subcmd, ObjectAddress fc_address)
{
	MemoryContext fc_oldcxt;
	CollectedATSubcmd *fc_newsub;

	/* 如果事件触发上下文未设置或收集被禁用，则忽略 */
	if (!currentEventTriggerState ||
		currentEventTriggerState->commandCollectionInhibited)
		return;

	Assert(IsA(fc_subcmd, AlterTableCmd));
	Assert(currentEventTriggerState->currentCommand != NULL);
	Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId));

	fc_oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);

	fc_newsub = palloc(sizeof(CollectedATSubcmd));
	fc_newsub->address = fc_address;
	fc_newsub->parsetree = copyObject(fc_subcmd);

	currentEventTriggerState->currentCommand->d.alterTable.subcmds =
		lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, fc_newsub);

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * EventTriggerAlterTableEnd
 *		完成保存 ALTER TABLE 命令，并将其添加到命令列表中。
 *
 * FIXME 此 API 没有考虑事务/子事务在执行过程中被中止的可能性。
 * 可能最好添加一个 AtEOSubXact_EventTriggers() 来解决这个问题。
 */
void EventTriggerAlterTableEnd(void)
{
	CollectedCommand *fc_parent;

	/* 如果事件触发上下文未设置或收集被禁用，则忽略 */
	if (!currentEventTriggerState ||
		currentEventTriggerState->commandCollectionInhibited)
		return;

	fc_parent = currentEventTriggerState->currentCommand->parent;

	/* 如果没有子命令，则不进行收集 */
	if (list_length(currentEventTriggerState->currentCommand->d.alterTable.subcmds) != 0)
	{
		MemoryContext fc_oldcxt;

		fc_oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);

		currentEventTriggerState->commandList =
			lappend(currentEventTriggerState->commandList,
					currentEventTriggerState->currentCommand);

		MemoryContextSwitchTo(fc_oldcxt);
	}
	else
		pfree(currentEventTriggerState->currentCommand);

	currentEventTriggerState->currentCommand = fc_parent;
}

/*
 * EventTriggerCollectGrant
 *		保存关于正在执行的 GRANT/REVOKE 命令的数据
 *
 * 此函数创建 InternalGrant 的副本，因为原始对象可能
 * 没有合适的生命周期。
 */
void EventTriggerCollectGrant(InternalGrant *fc_istmt)
{
	MemoryContext fc_oldcxt;
	CollectedCommand *fc_command;
	InternalGrant *fc_icopy;
	ListCell   *fc_cell;

	/* 如果事件触发上下文未设置或收集被禁用，则忽略 */
	if (!currentEventTriggerState ||
		currentEventTriggerState->commandCollectionInhibited)
		return;

	fc_oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);

	/*
	 * 这很繁琐，但很有必要。
	 */
	fc_icopy = palloc(sizeof(InternalGrant));
	memcpy(fc_icopy, fc_istmt, sizeof(InternalGrant));
	fc_icopy->objects = list_copy(fc_istmt->objects);
	fc_icopy->grantees = list_copy(fc_istmt->grantees);
	fc_icopy->col_privs = NIL;
	foreach(fc_cell, fc_istmt->col_privs)
		fc_icopy->col_privs = lappend(fc_icopy->col_privs, copyObject(lfirst(fc_cell)));

	/* 现在使用复制的 InternalGrant 进行收集 */
	fc_command = palloc(sizeof(CollectedCommand));
	fc_command->type = SCT_Grant;
	fc_command->in_extension = creating_extension;
	fc_command->d.grant.istmt = fc_icopy;
	fc_command->parsetree = NULL;

	currentEventTriggerState->commandList =
		lappend(currentEventTriggerState->commandList, fc_command);

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * EventTriggerCollectAlterOpFam
 *		保存关于正在执行的 ALTER OPERATOR FAMILY ADD/DROP 命令的数据
 */
void EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *fc_stmt, Oid fc_opfamoid,
							  List *fc_operators, List *fc_procedures)
{
	MemoryContext fc_oldcxt;
	CollectedCommand *fc_command;

	/* 如果事件触发上下文未设置或收集被禁用，则忽略 */
	if (!currentEventTriggerState ||
		currentEventTriggerState->commandCollectionInhibited)
		return;

	fc_oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);

	fc_command = palloc(sizeof(CollectedCommand));
	fc_command->type = SCT_AlterOpFamily;
	fc_command->in_extension = creating_extension;
	ObjectAddressSet(fc_command->d.opfam.address,
					 OperatorFamilyRelationId, fc_opfamoid);
	fc_command->d.opfam.operators = fc_operators;
	fc_command->d.opfam.procedures = fc_procedures;
	fc_command->parsetree = (Node *) copyObject(fc_stmt);

	currentEventTriggerState->commandList =
		lappend(currentEventTriggerState->commandList, fc_command);

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * EventTriggerCollectCreateOpClass
 *		保存关于正在执行的 CREATE OPERATOR CLASS 命令的数据
 */
void EventTriggerCollectCreateOpClass(CreateOpClassStmt *fc_stmt, Oid fc_opcoid,
								 List *fc_operators, List *fc_procedures)
{
	MemoryContext fc_oldcxt;
	CollectedCommand *fc_command;

	/* 如果事件触发上下文未设置或收集被禁用，则忽略 */
	if (!currentEventTriggerState ||
		currentEventTriggerState->commandCollectionInhibited)
		return;

	fc_oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);

	fc_command = palloc0(sizeof(CollectedCommand));
	fc_command->type = SCT_CreateOpClass;
	fc_command->in_extension = creating_extension;
	ObjectAddressSet(fc_command->d.createopc.address,
					 OperatorClassRelationId, fc_opcoid);
	fc_command->d.createopc.operators = fc_operators;
	fc_command->d.createopc.procedures = fc_procedures;
	fc_command->parsetree = (Node *) copyObject(fc_stmt);

	currentEventTriggerState->commandList =
		lappend(currentEventTriggerState->commandList, fc_command);

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * EventTriggerCollectAlterTSConfig
 *		保存关于正在执行的 ALTER TEXT SEARCH CONFIGURATION 命令的数据
 */
void EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *fc_stmt, Oid fc_cfgId,
								 Oid *fc_dictIds, int fc_ndicts)
{
	MemoryContext fc_oldcxt;
	CollectedCommand *fc_command;

	/* 如果事件触发上下文未设置或收集被禁用，则忽略 */
	if (!currentEventTriggerState ||
		currentEventTriggerState->commandCollectionInhibited)
		return;

	fc_oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);

	fc_command = palloc0(sizeof(CollectedCommand));
	fc_command->type = SCT_AlterTSConfig;
	fc_command->in_extension = creating_extension;
	ObjectAddressSet(fc_command->d.atscfg.address,
					 TSConfigRelationId, fc_cfgId);
	fc_command->d.atscfg.dictIds = palloc(sizeof(Oid) * fc_ndicts);
	memcpy(fc_command->d.atscfg.dictIds, fc_dictIds, sizeof(Oid) * fc_ndicts);
	fc_command->d.atscfg.ndicts = fc_ndicts;
	fc_command->parsetree = (Node *) copyObject(fc_stmt);

	currentEventTriggerState->commandList =
		lappend(currentEventTriggerState->commandList, fc_command);

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * EventTriggerCollectAlterDefPrivs
 *		保存关于正在执行的 ALTER DEFAULT PRIVILEGES 命令的数据
 */
void EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *fc_stmt)
{
	MemoryContext fc_oldcxt;
	CollectedCommand *fc_command;

	/* 如果事件触发上下文未设置或收集被禁用，则忽略 */
	if (!currentEventTriggerState ||
		currentEventTriggerState->commandCollectionInhibited)
		return;

	fc_oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);

	fc_command = palloc0(sizeof(CollectedCommand));
	fc_command->type = SCT_AlterDefaultPrivileges;
	fc_command->d.defprivs.objtype = fc_stmt->action->objtype;
	fc_command->in_extension = creating_extension;
	fc_command->parsetree = (Node *) copyObject(fc_stmt);

	currentEventTriggerState->commandList =
		lappend(currentEventTriggerState->commandList, fc_command);
	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * 在 ddl_command_end 事件触发器中，此函数报告正在运行的 DDL 命令。
 */
Datum pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
{
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	ListCell   *fc_lc;

	/*
	 * 保护此函数不在上下文外被调用
	 */
	if (!currentEventTriggerState)
		ereport(ERROR,
				(errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED),
				 errmsg("%s can only be called in an event trigger function",
						"pg_event_trigger_ddl_commands()")));

	/* 构建元组存储以保存结果行 */
	InitMaterializedSRF(fcinfo, 0);

	foreach(fc_lc, currentEventTriggerState->commandList)
	{
		CollectedCommand *fc_cmd = lfirst(fc_lc);
		Datum		fc_values[9];
		bool		fc_nulls[9];
		ObjectAddress fc_addr;
		int			fc_i = 0;

		/*
		 * 对于尝试创建已存在对象的 IF NOT EXISTS 命令，返回的 OID 是无效的。不要返回任何内容。
		 *
		 * 一个人可能会认为可行的替代方案是查找现有对象的 Oid 并使用该 Oid 进行解析。但
		 * 由于解析树可能与最初创建对象时的解析树不同，我们可能无法进入一致的
		 * 状态。
		 */
		if (fc_cmd->type == SCT_Simple &&
			!OidIsValid(fc_cmd->d.simple.address.objectId))
			continue;

		MemSet(fc_nulls, 0, sizeof(fc_nulls));

		switch (fc_cmd->type)
		{
			case SCT_Simple:
			case SCT_AlterTable:
			case SCT_AlterOpFamily:
			case SCT_CreateOpClass:
			case SCT_AlterTSConfig:
				{
					char	   *fc_identity;
					char	   *fc_type;
					char	   *fc_schema = NULL;

					if (fc_cmd->type == SCT_Simple)
						fc_addr = fc_cmd->d.simple.address;
					else if (fc_cmd->type == SCT_AlterTable)
						ObjectAddressSet(fc_addr,
										 fc_cmd->d.alterTable.classId,
										 fc_cmd->d.alterTable.objectId);
					else if (fc_cmd->type == SCT_AlterOpFamily)
						fc_addr = fc_cmd->d.opfam.address;
					else if (fc_cmd->type == SCT_CreateOpClass)
						fc_addr = fc_cmd->d.createopc.address;
					else if (fc_cmd->type == SCT_AlterTSConfig)
						fc_addr = fc_cmd->d.atscfg.address;

					/*
					 * 如果在同一命令中删除了一个对象，我们可能会遇到生成消息但
					 * 无法再查找对象信息的情况，因此跳过它以避免失败。
					 * 例如，这种情况可能发生在某些 ALTER TABLE 的子命令组合中。
					 */
					fc_identity = getObjectIdentity(&fc_addr, true);
					if (fc_identity == NULL)
						continue;

					/* 类型永远不会为 NULL。 */
					fc_type = getObjectTypeDescription(&fc_addr, true);

					/*
					 * 获取模式名称（如果有的话，“pg_temp” 如果是临时
					 * 对象）。如果对象类不在这里支持的列表中，我们假设它是无模式的对象类型，
					 * 因此“schema”保持为 NULL。
					 */
					if (is_objectclass_supported(fc_addr.classId))
					{
						AttrNumber	fc_nspAttnum;

						fc_nspAttnum = get_object_attnum_namespace(fc_addr.classId);
						if (fc_nspAttnum != InvalidAttrNumber)
						{
							Relation	fc_catalog;
							HeapTuple	fc_objtup;
							Oid			fc_schema_oid;
							bool		fc_isnull;

							fc_catalog = table_open(fc_addr.classId, AccessShareLock);
							fc_objtup = get_catalog_object_by_oid(fc_catalog,
															   get_object_attnum_oid(fc_addr.classId),
															   fc_addr.objectId);
							if (!HeapTupleIsValid(fc_objtup))
								elog(ERROR, "cache lookup failed for object %u/%u",
									 fc_addr.classId, fc_addr.objectId);
							fc_schema_oid =
								heap_getattr(fc_objtup, fc_nspAttnum,
											 RelationGetDescr(fc_catalog), &fc_isnull);
							if (fc_isnull)
								elog(ERROR,
									 "invalid null namespace in object %u/%u/%d",
									 fc_addr.classId, fc_addr.objectId, fc_addr.objectSubId);
							fc_schema = get_namespace_name_or_temp(fc_schema_oid);

							table_close(fc_catalog, AccessShareLock);
						}
					}

					/* classid */
					fc_values[fc_i++] = ObjectIdGetDatum(fc_addr.classId);
					/* objid */
					fc_values[fc_i++] = ObjectIdGetDatum(fc_addr.objectId);
					/* objsubid */
					fc_values[fc_i++] = Int32GetDatum(fc_addr.objectSubId);
					/* 命令标签 */
					fc_values[fc_i++] = CStringGetTextDatum(CreateCommandName(fc_cmd->parsetree));
					/* object_type */
					fc_values[fc_i++] = CStringGetTextDatum(fc_type);
					/* schema */
					if (fc_schema == NULL)
						fc_nulls[fc_i++] = true;
					else
						fc_values[fc_i++] = CStringGetTextDatum(fc_schema);
					/* identity */
					fc_values[fc_i++] = CStringGetTextDatum(fc_identity);
					/* in_extension */
					fc_values[fc_i++] = BoolGetDatum(fc_cmd->in_extension);
					/* command */
					fc_values[fc_i++] = PointerGetDatum(fc_cmd);
				}
				break;

			case SCT_AlterDefaultPrivileges:
				/* classid */
				fc_nulls[fc_i++] = true;
				/* objid */
				fc_nulls[fc_i++] = true;
				/* objsubid */
				fc_nulls[fc_i++] = true;
				/* 命令标签 */
				fc_values[fc_i++] = CStringGetTextDatum(CreateCommandName(fc_cmd->parsetree));
				/* object_type */
				fc_values[fc_i++] = CStringGetTextDatum(fc_stringify_adefprivs_objtype(fc_cmd->d.defprivs.objtype));
				/* schema */
				fc_nulls[fc_i++] = true;
				/* identity */
				fc_nulls[fc_i++] = true;
				/* in_extension */
				fc_values[fc_i++] = BoolGetDatum(fc_cmd->in_extension);
				/* command */
				fc_values[fc_i++] = PointerGetDatum(fc_cmd);
				break;

			case SCT_Grant:
				/* classid */
				fc_nulls[fc_i++] = true;
				/* objid */
				fc_nulls[fc_i++] = true;
				/* objsubid */
				fc_nulls[fc_i++] = true;
				/* 命令标签 */
				fc_values[fc_i++] = CStringGetTextDatum(fc_cmd->d.grant.istmt->is_grant ?
												  "GRANT" : "REVOKE");
				/* object_type */
				fc_values[fc_i++] = CStringGetTextDatum(fc_stringify_grant_objtype(fc_cmd->d.grant.istmt->objtype));
				/* schema */
				fc_nulls[fc_i++] = true;
				/* identity */
				fc_nulls[fc_i++] = true;
				/* in_extension */
				fc_values[fc_i++] = BoolGetDatum(fc_cmd->in_extension);
				/* command */
				fc_values[fc_i++] = PointerGetDatum(fc_cmd);
				break;
		}

		tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc,
							 fc_values, fc_nulls);
	}

	PG_RETURN_VOID();
}

/*
 * 返回对象类型作为字符串，正如在 GRANT 和
 * REVOKE 命令中出现的那样。
 */
static const char * fc_stringify_grant_objtype(ObjectType fc_objtype)
{
	switch (fc_objtype)
	{
		case OBJECT_COLUMN:
			return "COLUMN";
		case OBJECT_TABLE:
			return "TABLE";
		case OBJECT_SEQUENCE:
			return "SEQUENCE";
		case OBJECT_DATABASE:
			return "DATABASE";
		case OBJECT_DOMAIN:
			return "DOMAIN";
		case OBJECT_FDW:
			return "FOREIGN DATA WRAPPER";
		case OBJECT_FOREIGN_SERVER:
			return "FOREIGN SERVER";
		case OBJECT_FUNCTION:
			return "FUNCTION";
		case OBJECT_LANGUAGE:
			return "LANGUAGE";
		case OBJECT_LARGEOBJECT:
			return "LARGE OBJECT";
		case OBJECT_SCHEMA:
			return "SCHEMA";
		case OBJECT_PARAMETER_ACL:
			return "PARAMETER";
		case OBJECT_PROCEDURE:
			return "PROCEDURE";
		case OBJECT_ROUTINE:
			return "ROUTINE";
		case OBJECT_TABLESPACE:
			return "TABLESPACE";
		case OBJECT_TYPE:
			return "TYPE";
			/* 这些目前未使用 */
		case OBJECT_ACCESS_METHOD:
		case OBJECT_AGGREGATE:
		case OBJECT_AMOP:
		case OBJECT_AMPROC:
		case OBJECT_ATTRIBUTE:
		case OBJECT_CAST:
		case OBJECT_COLLATION:
		case OBJECT_CONVERSION:
		case OBJECT_DEFAULT:
		case OBJECT_DEFACL:
		case OBJECT_DOMCONSTRAINT:
		case OBJECT_EVENT_TRIGGER:
		case OBJECT_EXTENSION:
		case OBJECT_FOREIGN_TABLE:
		case OBJECT_INDEX:
		case OBJECT_MATVIEW:
		case OBJECT_OPCLASS:
		case OBJECT_OPERATOR:
		case OBJECT_OPFAMILY:
		case OBJECT_POLICY:
		case OBJECT_PUBLICATION:
		case OBJECT_PUBLICATION_NAMESPACE:
		case OBJECT_PUBLICATION_REL:
		case OBJECT_ROLE:
		case OBJECT_RULE:
		case OBJECT_STATISTIC_EXT:
		case OBJECT_SUBSCRIPTION:
		case OBJECT_TABCONSTRAINT:
		case OBJECT_TRANSFORM:
		case OBJECT_TRIGGER:
		case OBJECT_TSCONFIGURATION:
		case OBJECT_TSDICTIONARY:
		case OBJECT_TSPARSER:
		case OBJECT_TSTEMPLATE:
		case OBJECT_USER_MAPPING:
		case OBJECT_VIEW:
			elog(ERROR, "unsupported object type: %d", (int) fc_objtype);
	}

	return "???";				/* 保持编译器安静 */
}

/*
 * 返回对象类型作为字符串；如上所述，但在 ALTER DEFAULT PRIVILEGES 命令中使用拼写替代。
 * 通常这只是复数形式。
 */
static const char * fc_stringify_adefprivs_objtype(ObjectType fc_objtype)
{
	switch (fc_objtype)
	{
		case OBJECT_COLUMN:
			return "COLUMNS";
		case OBJECT_TABLE:
			return "TABLES";
		case OBJECT_SEQUENCE:
			return "SEQUENCES";
		case OBJECT_DATABASE:
			return "DATABASES";
		case OBJECT_DOMAIN:
			return "DOMAINS";
		case OBJECT_FDW:
			return "FOREIGN DATA WRAPPERS";
		case OBJECT_FOREIGN_SERVER:
			return "FOREIGN SERVERS";
		case OBJECT_FUNCTION:
			return "FUNCTIONS";
		case OBJECT_LANGUAGE:
			return "LANGUAGES";
		case OBJECT_LARGEOBJECT:
			return "LARGE OBJECTS";
		case OBJECT_SCHEMA:
			return "SCHEMAS";
		case OBJECT_PROCEDURE:
			return "PROCEDURES";
		case OBJECT_ROUTINE:
			return "ROUTINES";
		case OBJECT_TABLESPACE:
			return "TABLESPACES";
		case OBJECT_TYPE:
			return "TYPES";
			/* 这些目前未使用 */
		case OBJECT_ACCESS_METHOD:
		case OBJECT_AGGREGATE:
		case OBJECT_AMOP:
		case OBJECT_AMPROC:
		case OBJECT_ATTRIBUTE:
		case OBJECT_CAST:
		case OBJECT_COLLATION:
		case OBJECT_CONVERSION:
		case OBJECT_DEFAULT:
		case OBJECT_DEFACL:
		case OBJECT_DOMCONSTRAINT:
		case OBJECT_EVENT_TRIGGER:
		case OBJECT_EXTENSION:
		case OBJECT_FOREIGN_TABLE:
		case OBJECT_INDEX:
		case OBJECT_MATVIEW:
		case OBJECT_OPCLASS:
		case OBJECT_OPERATOR:
		case OBJECT_OPFAMILY:
		case OBJECT_PARAMETER_ACL:
		case OBJECT_POLICY:
		case OBJECT_PUBLICATION:
		case OBJECT_PUBLICATION_NAMESPACE:
		case OBJECT_PUBLICATION_REL:
		case OBJECT_ROLE:
		case OBJECT_RULE:
		case OBJECT_STATISTIC_EXT:
		case OBJECT_SUBSCRIPTION:
		case OBJECT_TABCONSTRAINT:
		case OBJECT_TRANSFORM:
		case OBJECT_TRIGGER:
		case OBJECT_TSCONFIGURATION:
		case OBJECT_TSDICTIONARY:
		case OBJECT_TSPARSER:
		case OBJECT_TSTEMPLATE:
		case OBJECT_USER_MAPPING:
		case OBJECT_VIEW:
			elog(ERROR, "unsupported object type: %d", (int) fc_objtype);
	}

	return "???";				/* 保持编译器安静 */
}
